﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using snat.model;
using snat.algorithms;
using snat.util;
using System.IO;

namespace algorithms.algorithms.Metrics {
    public class ClusteringCoefficient : IAlgorithm, ITextRenderable, IChartRenderable, IReaderWriter {
        public string Name {
            get { return "Clustering Coefficient"; }
        }

        // Output Variables
        Dictionary<BaseNode, double> clusterCoList = new Dictionary<BaseNode, double>();
        private double avgClusterCo = 0;

        // Number of edges in neighbourhood over nodecount in neighbourhood * nodecount in neighbourhood - 1
        public void Process() {
            avgClusterCo = 0;
            clusterCoList.Clear();

            Dictionary<int, HashSet<BaseNode>> neighbourhoods = new Dictionary<int,HashSet<BaseNode>>();

            int asdf = 0;
            foreach (BaseNode node in NetworkModel.Instance.Nodes.Values) {

                var neighbourhood = from c in new SortedSet<Edge>(node.OutEdges.Union(node.InEdges), new EdgeComparer())
                                   where !(c.Source == node && c.Target == node) //no self edges
                                   select c.Source == node ? c.Target : c.Source;

                var edgeCollection = from n in neighbourhood
                                     select new SortedSet<Edge>(n.OutEdges.Union(n.InEdges), new EdgeComparer());

                var validEdgeCollection = from edges in edgeCollection
                                          select
                                             from edge in edges
                                             where edge.Source!=edge.Target && neighbourhood.Contains(edge.Source) && neighbourhood.Contains(edge.Target)
                                             select edge;


                SortedSet<Edge> validEdges = new SortedSet<Edge>(new EdgeComparer());

                // some edges are in there twice if it is a bidirectional link.

                foreach (var subset in validEdgeCollection) {
                    validEdges.UnionWith(subset);
                }
               
                double clusteringCoefficient = 0;
                if (neighbourhood.Count<BaseNode>() > 1) {
                    clusteringCoefficient = ((double)validEdges.Count) / (double)(neighbourhood.Count() * (neighbourhood.Count() - 1));
                    if (clusteringCoefficient > 1) {
                        Console.WriteLine((asdf++)+" "+node.Name + " :: ClusterCo=" + clusteringCoefficient + " <<<< ERROR");
                        Console.ReadLine();
                    }
                    Console.WriteLine(node.Name + " :: ClusterCo=" + clusteringCoefficient);
                    avgClusterCo += clusteringCoefficient;
                }
                clusterCoList.Add(node, clusteringCoefficient);
            }
            Console.WriteLine("..");

            if (NetworkModel.Instance.Nodes.Count > 1) {
                avgClusterCo /= NetworkModel.Instance.Nodes.Count;
            }
            if (RenderText != null) RenderText(this);   // Call render text
            if (RenderChart != null) RenderChart(this); // Call render chart
        }

        public void Register() {
            NetworkModel.Instance.TapeLoaded += Process;
            NetworkModel.Instance.TapeEnded += Process;
        }

        public void DeRegister() {
            NetworkModel.Instance.TapeLoaded -= Process;
            NetworkModel.Instance.TapeEnded -= Process;
        }

        public event RenderTextDelegate RenderText;

        public string GetRenderableText() {
            return String.Format("Average Local Clustering Coefficient is {0}", avgClusterCo);
        }

        public event RenderChartDelegate RenderChart;

        public snat.util.DataChart GetRenderableChart() {
            DataChartOptions opts = new DataChartOptions("Local Clustering Coefficient");
            opts.Description = "This chart gives the Clustering Coefficient for each node in the network. Clustering Coefficient is a measure of degree to which nodes in a graph tend to cluster together. For datasets where the number of nodes exceeds 15 only the top five and bottom five nodes are displayed, and the rest are collated and averaged. Note: This is best viewed as a bar chart.";
            DataSeries series1 = new DataSeries("Clustering Coefficient");

            // Build small chart for large datasets
            if (clusterCoList.Count() > 15) {
                Dictionary<BaseNode, double> clusterCoListNew = new Dictionary<BaseNode, double>();

                // Get 3 sets - top 5, bottom 5, and 'the rest' coefficient values
                Dictionary<BaseNode, double> topFive = clusterCoList.Keys.OrderBy(e => clusterCoList[e]).Take(5).ToDictionary<BaseNode, BaseNode, double>(k => k, v => clusterCoList[v]);
                Dictionary<BaseNode, double> bottomFive = clusterCoList.Keys.OrderByDescending(e => clusterCoList[e]).Take(5).ToDictionary<BaseNode, BaseNode, double>(k => k, v => clusterCoList[v]);
                Dictionary<BaseNode, double> rest = clusterCoList.Keys.Where(k => !topFive.ContainsKey(k) && !bottomFive.ContainsKey(k)).ToDictionary<BaseNode, BaseNode, double>(k => k, v => clusterCoList[v]);

                // Build series1
                foreach (KeyValuePair<BaseNode, double> p in topFive) series1.Series.Add(new DataPoint(p.Key.Name, p.Value));
                series1.Series.Add(new DataPoint("Others (Avg)", rest.Values.Average()));
                foreach (KeyValuePair<BaseNode, double> p in bottomFive) series1.Series.Add(new DataPoint(p.Key.Name, p.Value));

            } else {
                // Build full chart for small datasets
                foreach (KeyValuePair<BaseNode, double> p in clusterCoList) {
                    series1.Series.Add(new DataPoint(p.Key.Name, p.Value));
                }
            }
            return new DataChart(opts, series1);
        }

        public void Write(string filePath) {
            using (StreamWriter output = new StreamWriter(filePath)) {
                output.WriteLine(GetRenderableText());
            }
        }

        public string Read(string filePath) {
            String ret = String.Empty;
            using (StreamReader input = new StreamReader(filePath)) {
                ret += input.ReadLine();
            }
            return ret;
        }
    }

    public class EdgeComparer : IComparer<Edge> {
        public int Compare(Edge x, Edge y) {
            if ((x.Source.Equals(y.Source) &&
                x.Target.Equals(y.Target)) ||
                (x.Source.Equals(y.Target) &&
                x.Target.Equals(y.Source)))
                return 0;
            return 1;
        }
    }
}
